﻿using S7Cat.Enums;
using S7Cat.Models;
using S7Cat.Protocol;
using S7Cat.Tools;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace S7Cat
{
    public abstract partial class BasePlc
    {
        #region 单个地址的读写
        internal abstract Task<MemoryOwner<byte>> ReadBytesPooledAsync(DataItemAddress address, CancellationToken cancellationToken = default);

        public abstract Task<byte[]> ReadBytesAsync(DataItemAddress address, CancellationToken cancellationToken = default);

        /// <summary>
        /// 已经根据PDU分割后的读取
        /// </summary>
        /// <param name="address"></param>
        /// <param name="buffer"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        internal async Task ReadBytesSplitAsync(DataItemAddress address, Memory<byte> buffer, CancellationToken cancellationToken = default)
        {
            using var request = ConnectionRequest.BuildSplitReadRequest(address);
            using var data = await RequestTsduAsync(request.WrittenMemory, cancellationToken).ConfigureAwait(false);

            var result = data.Span;

            if (result.Length < 18)
            {
                throw new Exception($"response error, request data : {Convert.ToHexString(result)}");
            }
            PackageParser.ValidateResponseCode((ReadWriteErrorCode)result[14]);
            result.Slice(18, address.ByteCount).CopyTo(buffer.Span);
        }

        /// <summary>
        /// 根据地址读取字节数组
        /// </summary>
        /// <param name="address"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>        

        public async Task<T> ReadAsync<T>(DataItemAddress address, CancellationToken cancellationToken = default) where T : unmanaged, INumber<T>
        {
            using var buffer = await ReadBytesPooledAsync(address, cancellationToken).ConfigureAwait(false);

            var span = buffer.Span;
            span.Reverse();

            return Unsafe.ReadUnaligned<T>(ref span[0]);
        }

        public async Task<bool> ReadAsync(DataItemAddress address, CancellationToken cancellationToken = default)
        {
            var b = await ReadAsync<byte>(address, cancellationToken).ConfigureAwait(false);
            return ReadBool(b, 7 - address.BitAdr);
        }

        public async Task<T> ReadStructAsync<T>(DataItemAddress address, CancellationToken cancellationToken = default) where T : struct, IPlcStruct<T>
        {
            using var buffer = await ReadBytesPooledAsync(address, cancellationToken).ConfigureAwait(false);
            T.Deserialize(buffer.Span, out var value);
            return value;
        }


        public abstract Task WriteBytesAsync(DataItemAddress address, Memory<byte> bytes, CancellationToken cancellationToken = default);

        /// <summary>
        /// 已经根据PDU分割后的写入
        /// </summary>
        /// <param name="address"></param>
        /// <param name="bytes"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        internal async Task WriteBytesSplitAsync(DataItemAddress address, Memory<byte> bytes, CancellationToken cancellationToken = default)
        {
            using var request = ConnectionRequest.BuildSplitWriteRequest(address, bytes.Span);

            using var data = await RequestTsduAsync(request.WrittenMemory, cancellationToken).ConfigureAwait(false);
            PackageParser.ValidateWriteResponse(data.Span, address);
        }

        public Task WriteAsync<T>(DataItemAddress address, T value, CancellationToken cancellationToken = default) where T : unmanaged, INumber<T>
        {
            var span = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1));
            span.Reverse();

            using var buffer = MemoryOwner<byte>.Allocate(span.Length);
            span.CopyTo(buffer.Span);

            return WriteBytesSplitAsync(address, buffer.Memory, cancellationToken);
        }

        public Task WriteAsync(DataItemAddress address, bool value, CancellationToken cancellationToken = default)
        {
            return WriteBytesSplitAsync(address, value ? [1] : new byte[1] { 0 }, cancellationToken);
        }

        public Task WriteStructAsync<T>(DataItemAddress address, T value, CancellationToken cancellationToken = default) where T : struct, IPlcStruct<T>
        {
            using var writer = new ArrayPoolBufferWriter<byte>(T.ByteCount);
            T.Serialize(value, writer);

            return WriteBytesAsync(address, writer.Memory, cancellationToken);
        }
        #endregion

        #region 多个地址的读写
        internal abstract Task<byte[]> ReadMultipleAsync(Memory<DataItemAddress> addresses, CancellationToken cancellationToken = default);

        public abstract Task<MultipleReader> ReadMultipleReaderAsync(Memory<DataItemAddress> addresses, CancellationToken cancellationToken = default);

        public abstract Task WriteMultipleAsync(Memory<DataItemAddress> addresses, Memory<byte> dataBytes, CancellationToken cancellationToken = default);

        public Task WriteMultipleWriterAsync(MultipleWriter writer, CancellationToken cancellationToken = default)
        {
            return WriteMultipleAsync(writer.Addresses, writer.DataBytes, cancellationToken);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        protected static int GetByteCount(ReadOnlySpan<DataItemAddress> addresses)
        {
            var totalSize = 0;
            foreach (var address in addresses)
            {
                totalSize += address.ByteCount;
            }
            return totalSize;
        }
        #endregion

        #region 字符串地址
        public Task<T> ReadAsync<T>(string address, CancellationToken cancellationToken = default) where T : unmanaged, INumber<T>
        {
            var item = AddressParser.Parse(address);
            return ReadAsync<T>(item, cancellationToken);
        }

        public Task<bool> ReadAsync(string address, CancellationToken cancellationToken = default)
        {
            var item = AddressParser.Parse(address);
            return ReadAsync(item, cancellationToken);
        }

        public Task<T> ReadStructAsync<T>(string address, CancellationToken cancellationToken = default) where T : struct, IPlcStruct<T>
        {
            var item = AddressParser.Parse(address);
            return ReadStructAsync<T>(item, cancellationToken);
        }

        public Task WriteAsync<T>(string address, T value, CancellationToken cancellationToken = default) where T : unmanaged, INumber<T>
        {
            var item = AddressParser.Parse(address);
            return WriteAsync(item, value, cancellationToken);
        }

        public Task WriteAsync(string address, bool value, CancellationToken cancellationToken = default)
        {
            var item = AddressParser.Parse(address);
            return WriteAsync(item, value, cancellationToken);
        }

        public Task WriteStructAsync<T>(string address, T value, CancellationToken cancellationToken = default) where T : struct, IPlcStruct<T>
        {
            var item = AddressParser.Parse(address);
            return WriteStructAsync(item, value, cancellationToken);
        }
        #endregion

        #region Clock
        public async Task<DateTime> ReadClockAsync(CancellationToken cancellationToken = default)
        {
            using var request = ConnectionRequest.BuildReadClockRequest();

            using var data = await RequestTsduAsync(request.WrittenMemory, cancellationToken).ConfigureAwait(false);

            return PackageParser.ParseReadClockResponse(data.Span);
        }

        public async Task WriteClockAsync(DateTime dateTime, CancellationToken cancellationToken = default)
        {
            using var request = ConnectionRequest.BuildWriteClockRequest(dateTime);

            using var data = await RequestTsduAsync(request.WrittenMemory, cancellationToken).ConfigureAwait(false);

            var span = data.Span;
            PackageParser.AssertPduResult(span);
            PackageParser.AssertUserDataResult(span, 0x0a);
        }
        #endregion

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static bool ReadBool(byte data, int bitAdr)
        {
            return ((byte)(data << bitAdr) >> 7) == 0b_0000_0001;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static byte SetBool(byte data, int bitAdr, bool value)
        {
            return value == true ? (byte)(data | (byte)(0b_1000_0000 >> bitAdr)) : (byte)(data & ~(byte)(0b_1000_0000 >> bitAdr));
        }
    }
}
